/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Bio.Data.Interfaces;
using Bio.Views.Structure.Editor.Internal.Editing;
using Bio.Views.Structure.Editor.Internal.Rendering.Interfaces;
using Bio.Views.Structure.Editor.Internal.Rendering.Primitives;

namespace Bio.Views.Structure.Editor.Internal.Rendering.Visuals
{
    public class NucleotideStructureElementVisualComponent : DependencyObject, IStructureElementVisualComponent
    {
        #region Events

        //Fire this event if you want any capable parent components to deliver data-driven defaults....its not necessary to fire this event to get defaults provided from the parent tree.
        public event EventHandler StartingNucleotidePropertyResolve;

        private void OnStartingNucleotidePropertyResolve()
        {
            if (StartingNucleotidePropertyResolve != null)
                StartingNucleotidePropertyResolve(this, EventArgs.Empty);
        }

        //Must fire this event if you fired a StartingNucleotidePropertyResolve event, of the parent tree could get corrupted from a default property providing point of view.
        public event EventHandler EndingNucleotidePropertyResolve;

        private void OnEndingNucleotidePropertyResolve()
        {
            if (EndingNucleotidePropertyResolve != null)
                EndingNucleotidePropertyResolve(this, EventArgs.Empty);
        }

        public event EventHandler NotifyNucleotideVisualEdited;

        private void OnNucleotideEdited()
        {
            if (NotifyNucleotideVisualEdited != null && !PauseEditNotification)
                NotifyNucleotideVisualEdited(this, EventArgs.Empty);
        }

        #endregion

        #region Dependency Properties

        public static readonly DependencyProperty NucleotideFontFamilyProperty = DependencyProperty.Register("NucleotideFontFamily",
            typeof(FontFamily), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontWeightProperty = DependencyProperty.Register("NucleotideFontWeight",
            typeof(FontWeight?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontStyleProperty = DependencyProperty.Register("NucleotideFontStyle",
            typeof(FontStyle?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontSizeProperty = DependencyProperty.Register("NucleotideFontSize",
            typeof(double?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideRenderingStyleProperty = DependencyProperty.Register("NucleotideRenderingStyle",
            typeof(NucleotideRenderingMode?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFillProperty = DependencyProperty.Register("NucleotideFill",
            typeof(Brush), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty IsNucleotideSubstitutedProperty = DependencyProperty.Register("IsNucleotideSubstituted",
            typeof(bool?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideSubstitutionProperty = DependencyProperty.Register("NucleotideSubstitution",
            typeof(char?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleDiameterProperty = DependencyProperty.Register("NucleotideCircleDiameter",
            typeof(double?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleEdgeThicknessProperty = DependencyProperty.Register("NucleotideCircleEdgeThickness",
            typeof(double?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));
        
        public static readonly DependencyProperty NucleotideCircleIsFilledProperty = DependencyProperty.Register("NucleotideCircleIsFilled",
            typeof(bool?), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleFillProperty = DependencyProperty.Register("NucleotideCircleFill",
            typeof(Brush), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));
        
        public static readonly DependencyProperty NucleotideCircleStrokeProperty = DependencyProperty.Register("NucleotideCircleStroke",
            typeof(Brush), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnVisualPropertyChanged));

        public static readonly DependencyProperty SelectionFillProperty = DependencyProperty.Register("SelectionFill",
            typeof(Brush), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.DarkGoldenrod), OnVisualPropertyChanged));

        public Brush SelectionFill
        {
            get { return (Brush)GetValue(SelectionFillProperty); }
            set { SetValue(SelectionFillProperty, value); }
        }

        public static readonly DependencyProperty ContextualSelectionFillProperty = DependencyProperty.Register("ContextualSelectionFill",
            typeof(Brush), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Red), OnVisualPropertyChanged));

        public Brush ContextualSelectionFill
        {
            get { return (Brush)GetValue(ContextualSelectionFillProperty); }
            set { SetValue(ContextualSelectionFillProperty, value); }
        }

        private static readonly DependencyPropertyKey DataPropertyKey = DependencyProperty.RegisterReadOnly("Data",
            typeof(IBioSymbol), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty DataProperty = DataPropertyKey.DependencyProperty;

        public IBioSymbol Data
        {
            get { return (IBioSymbol)GetValue(DataProperty); }
            private set { SetValue(DataPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey NucleotideIndexPropertyKey = DependencyProperty.RegisterReadOnly("NucleotideIndex",
            typeof(int), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty NucleotideIndexProperty = NucleotideIndexPropertyKey.DependencyProperty;

        public int NucleotideIndex
        {
            get { return (int)GetValue(NucleotideIndexProperty); }
            private set { SetValue(NucleotideIndexPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey LocationPropertyKey = DependencyProperty.RegisterReadOnly("Location",
            typeof(Point), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty LocationProperty = LocationPropertyKey.DependencyProperty;

        public Point Location
        {
            get { return (Point)GetValue(LocationProperty); }
            private set { SetValue(LocationPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey RenderedSizePropertyKey = DependencyProperty.RegisterReadOnly("RenderedSize",
            typeof(Size), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(new Size(0.0, 0.0)));
        public static readonly DependencyProperty RenderedSizeProperty = RenderedSizePropertyKey.DependencyProperty;

        public Size RenderedSize
        {
            get { return (Size)GetValue(RenderedSizeProperty); }
            private set { SetValue(RenderedSizePropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ContextMessagePropertyKey = DependencyProperty.RegisterReadOnly("ContextMessage",
            typeof(string), typeof(NucleotideStructureElementVisualComponent), new FrameworkPropertyMetadata(null));
        public static readonly DependencyProperty ContextMessageProperty = ContextMessagePropertyKey.DependencyProperty;

        public string ContextMessage
        {
            get { return (string)GetValue(ContextMessageProperty); }
            private set { SetValue(ContextMessagePropertyKey, value); }
        }

        private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            NucleotideStructureElementVisualComponent ntVisual = d as NucleotideStructureElementVisualComponent;
            if (ntVisual != null)
            {
                if (args.Property == NucleotideFontFamilyProperty ||
                    args.Property == NucleotideFontSizeProperty ||
                    args.Property == NucleotideFontWeightProperty ||
                    args.Property == NucleotideFontStyleProperty ||
                    args.Property == NucleotideCircleDiameterProperty ||
                    args.Property == NucleotideCircleEdgeThicknessProperty ||
                    args.Property == NucleotideRenderingStyleProperty ||
                    args.Property == IsNucleotideSubstitutedProperty ||
                    args.Property == NucleotideSubstitutionProperty)
                {
                    ntVisual.Bounds = ntVisual.PhysicalNtVisual.Measure();
                    ntVisual.PhysicalNtVisual.Render();
                }
                else if (args.Property == NucleotideCircleIsFilledProperty ||
                    args.Property == NucleotideCircleFillProperty ||
                    args.Property == NucleotideCircleStrokeProperty ||
                    args.Property == NucleotideFillProperty ||
                    (args.Property == SelectionFillProperty && ntVisual.IsSelected) ||
                    (args.Property == ContextualSelectionFillProperty && ntVisual.IsContextuallySelected))
                    ntVisual.PhysicalNtVisual.Render();

                if (args.Property == NucleotideRenderingStyleProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideRenderingStyle = args.NewValue as NucleotideRenderingMode?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideFontWeightProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideFontWeight = args.NewValue as FontWeight?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideFontStyleProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideFontStyle = args.NewValue as FontStyle?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideFontFamilyProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideFontFamily = args.NewValue as FontFamily;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if(args.Property == NucleotideFontSizeProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideFontSize = args.NewValue as double?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideFillProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideFill = args.NewValue as Brush;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideSubstitutionProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideSubstitution = args.NewValue as char?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == IsNucleotideSubstitutedProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.IsNucleotideSubstituted = args.NewValue as bool?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideCircleDiameterProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideCircleDiameter = args.NewValue as double?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideCircleEdgeThicknessProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideCircleEdgeThickness = args.NewValue as double?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideCircleFillProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideCircleFill = args.NewValue as Brush;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideCircleIsFilledProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideCircleIsFilled = args.NewValue as bool?;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }

                if (args.Property == NucleotideCircleStrokeProperty)
                {
                    if (ntVisual.PropertyContextManager != null && !ntVisual.PauseEditTracking)
                    {
                        ntVisual.PropertyContextManager.NucleotideCircleStroke = args.NewValue as Brush;
                        ntVisual.OnNucleotideEdited();
                    }
                    return;
                }
            }
        }

        #endregion

        #region Properties

        private Rect _bounds;
        public Rect Bounds
        {
            get { return _bounds; }
            private set
            {
                _bounds = value;
                RenderedSize = new Size(_bounds.Width, _bounds.Height);
            }
        }

        public NucleotideVector NucleotideConnector 
        { 
            get; 
            private set; 
        }

        #endregion

        #region IStructureElementVisualComponent Members

        public IEnumerable<int> NucleotideIndices
        {
            get { return Enumerable.Range(NucleotideIndex, 1); }
        }

        public Visual VisualComponent
        {
            get { return PhysicalNtVisual; }
        }

        public IEnumerable<IStructureElementVisualComponent> Children
        {
            get { return Enumerable.Empty<IStructureElementVisualComponent>(); }
        }

        private IStructureElementVisualComponent _parentComponent;
        public IStructureElementVisualComponent ParentComponent
        {
            get { return _parentComponent; }
            set 
            { 
                _parentComponent = value;
                NotifyParentHierarchyChanged();
            }
        }

        public bool IsComponentVisible
        {
            get { return _isComponentVisible; }
            set
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.Visible = value;
                    OnNucleotideEdited();
                }
                _isComponentVisible = value;
                PhysicalNtVisual.Render();
            }
        }

        public bool IsSelected
        {
            get { return _isSelected; }
            private set
            {
                if (!IsSelectable && value)
                    return;
                _isSelected = value;
                if (_isSelected && _isContextuallySelected)
                    _isContextuallySelected = false; //Turn off contextual selection if were directly selected.
                PhysicalNtVisual.Render();
            }
        }

        public bool IsContextuallySelected
        {
            get { return _isContextuallySelected; }
            private set
            {
                _isContextuallySelected = value;
                if (_isContextuallySelected && _isSelected)
                    _isSelected = false; //Turn off direct selection if were contextually selected.
                PhysicalNtVisual.Render();
            }
        }

        public bool ShowNucleotideConnectors
        {
            get { return _showNucleotideConnectors; }
            private set
            {
                _showNucleotideConnectors = value;
                PhysicalNtVisual.Render();
            }
        }

        public bool HitTest(Point point, bool includeChildren)
        {
            return Bounds.Contains(point);
        }

        public void AddChild(IStructureElementVisualComponent child)
        {
            Bounds = PhysicalNtVisual.Measure();
        }

        public void RemoveChild(IStructureElementVisualComponent child)
        {
            //Empty becuase this is a logical leaf node.
        }

        public void MakeSelectable()
        {
            IsSelectable = true;
        }

        public void MakeUnselectable()
        {
            IsSelectable = false; 
        }

        public void Select(bool showNucleotideConnectors)
        {
            IsSelected = true;
            ShowNucleotideConnectors = showNucleotideConnectors;
        }

        public void Deselect()
        {
            IsSelected = false;
            ShowNucleotideConnectors = false;
        }

        public void ContextuallySelect()
        {
            IsContextuallySelected = true;
        }

        public void ContextuallyDeselect()
        {
            IsContextuallySelected = false;
        }

        public void PropagateVisualPropertyChanged()
        {
            Bounds = PhysicalNtVisual.Measure();
            PhysicalNtVisual.Render();
        }

        public Visual MakeVisualSnapshot()
        {
            NucleotideDrawingVisual visual = new NucleotideDrawingVisual(this);
            visual.Measure();
            visual.Render();
            return visual;
        }

        public void NotifyParentHierarchyChanged()
        {
            ContextMessage = BuildContextMessage();
        }

        public FontFamily NucleotideFontFamily
        {
            get 
            { 
                FontFamily val = GetValue(NucleotideFontFamilyProperty) as FontFamily;
                if (val == null)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        FontFamily inheritedFontFamily = ParentComponent.NucleotideFontFamily;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedFontFamily != null)
                            return inheritedFontFamily;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_FONTFAMILY;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFontFamily = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideFontFamilyProperty, value);
                UninterruptEditNotification();
            }
        }

        public FontWeight? NucleotideFontWeight
        {
            get 
            {
                FontWeight? val = (FontWeight?)GetValue(NucleotideFontWeightProperty);
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        FontWeight? inheritedFontWeight = ParentComponent.NucleotideFontWeight;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedFontWeight.HasValue)
                            return inheritedFontWeight;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_FONTWEIGHT;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFontWeight = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideFontWeightProperty, value);
                UninterruptEditNotification();
            }
        }

        public FontStyle? NucleotideFontStyle
        {
            get 
            { 
                FontStyle? val = (FontStyle?)GetValue(NucleotideFontStyleProperty);
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        FontStyle? inheritedFontStyle = ParentComponent.NucleotideFontStyle;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedFontStyle.HasValue)
                            return inheritedFontStyle;
                    }
                    return INTERNAL_DEFFAULT_NUCLEOTIDE_FONTSTYLE;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFontStyle = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideFontStyleProperty, value);
                UninterruptEditNotification();
            }
        }

        public double? NucleotideFontSize
        {
            get 
            { 
                double? val = (double?)GetValue(NucleotideFontSizeProperty);
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        double? inheritedFontSize = ParentComponent.NucleotideFontSize;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedFontSize.HasValue && inheritedFontSize.Value > 0.0)
                            return inheritedFontSize;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_FONTSIZE;
                }
                else 
                {
                    if (val.Value > 0.0)
                        return val;
                    return INTERNAL_DEFAULT_NUCLEOTIDE_FONTSIZE;
                }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFontSize = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideFontSizeProperty, value);
                UninterruptEditNotification();
            }
        }

        public NucleotideRenderingMode? NucleotideRenderingStyle
        {
            get 
            { 
                NucleotideRenderingMode? val = (NucleotideRenderingMode?)GetValue(NucleotideRenderingStyleProperty);
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        NucleotideRenderingMode? inheritedRenderingMode = ParentComponent.NucleotideRenderingStyle;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedRenderingMode.HasValue)
                            return inheritedRenderingMode;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_RENDERINGMODE;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideRenderingStyle = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideRenderingStyleProperty, value);
                UninterruptEditNotification();
            }
        }

        public Brush NucleotideFill
        {
            get 
            { 
                Brush val = GetValue(NucleotideFillProperty) as Brush;
                if (val == null)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        Brush inheritedNucleotideFill = ParentComponent.NucleotideFill;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedNucleotideFill != null)
                            return inheritedNucleotideFill;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_FILL;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFill = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideFillProperty, value);
                UninterruptEditNotification();
            }
        }

        public char? NucleotideSubstitution 
        {
            get
            {
                char? val = GetValue(NucleotideSubstitutionProperty) as char?;
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        char? inheritedNucleotideSubstitution = ParentComponent.NucleotideSubstitution;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedNucleotideSubstitution.HasValue)
                            return inheritedNucleotideSubstitution;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_SUBSTITUTION;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideSubstitution = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideSubstitutionProperty, value);
                UninterruptEditNotification();
            }
        }

        public bool? IsNucleotideSubstituted 
        {
            get
            {
                bool? val = GetValue(IsNucleotideSubstitutedProperty) as bool?;
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        bool? inheritedIsNucleotideSubstituted = ParentComponent.IsNucleotideSubstituted;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedIsNucleotideSubstituted.HasValue)
                            return inheritedIsNucleotideSubstituted;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_ISSUBSTITUTED;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.IsNucleotideSubstituted = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(IsNucleotideSubstitutedProperty, value);
                UninterruptEditNotification();
            } 
        }

        public double? NucleotideCircleDiameter 
        {
            get
            {
                double? val = GetValue(NucleotideCircleDiameterProperty) as double?;
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        double? inheritedNucleotideCircleDiameter = ParentComponent.NucleotideCircleDiameter;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedNucleotideCircleDiameter.HasValue && inheritedNucleotideCircleDiameter.Value > 0.0)
                            return inheritedNucleotideCircleDiameter;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_DIAMETER;
                }
                else
                {
                    if (val.Value > 0.0)
                        return val;
                    return INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_DIAMETER;
                }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleDiameter = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideCircleDiameterProperty, value);
                UninterruptEditNotification();
            }
        }

        public double? NucleotideCircleEdgeThickness 
        {
            get
            {
                double? val = GetValue(NucleotideCircleEdgeThicknessProperty) as double?;
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        double? inheritedNucleotideCircleEdgeThickness = ParentComponent.NucleotideCircleEdgeThickness;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedNucleotideCircleEdgeThickness.HasValue && inheritedNucleotideCircleEdgeThickness >= 0.0)
                            return inheritedNucleotideCircleEdgeThickness;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_EDGETHICKENESS; 
                }
                else
                {
                    if (val.Value > 0.0)
                        return val;
                    return INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_EDGETHICKENESS;
                }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleEdgeThickness = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideCircleEdgeThicknessProperty, value);
                UninterruptEditNotification();
            }
        }

        public bool? NucleotideCircleIsFilled 
        {
            get
            {
                bool? val = GetValue(NucleotideCircleIsFilledProperty) as bool?;
                if (!val.HasValue)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        bool? inheritedNucleotideCircleIsFilled = ParentComponent.NucleotideCircleIsFilled;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedNucleotideCircleIsFilled.HasValue)
                            return inheritedNucleotideCircleIsFilled;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_ISFILLED;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleIsFilled = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideCircleIsFilledProperty, value);
                UninterruptEditNotification();
            }
        }

        public Brush NucleotideCircleFill 
        {
            get
            {
                Brush val = GetValue(NucleotideCircleFillProperty) as Brush;
                if (val == null)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        Brush inheritedNucleotideCircleFill = ParentComponent.NucleotideCircleFill;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedNucleotideCircleFill != null)
                            return inheritedNucleotideCircleFill;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_FILL;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleFill = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideCircleFillProperty, value);
                UninterruptEditNotification();
            }
        }

        public Brush NucleotideCircleStroke 
        {
            get
            {
                Brush val = GetValue(NucleotideCircleStrokeProperty) as Brush;
                if (val == null)
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                    {
                        OnStartingNucleotidePropertyResolve();
                        Brush inheritedNucleotideCircleStoke = ParentComponent.NucleotideCircleStroke;
                        OnEndingNucleotidePropertyResolve();
                        if (inheritedNucleotideCircleStoke != null)
                            return inheritedNucleotideCircleStoke;
                    }
                    return INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_STROKE;
                }
                else { return val; }
            }
            set 
            {
                if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleStroke = value;
                    OnNucleotideEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.
                SetValue(NucleotideCircleStrokeProperty, value);
                UninterruptEditNotification();
            }
        }

        public bool? BasePairConnectorIsFilled 
        {
            get { return null; }
            set { throw new NotSupportedException(); }
        }

        public bool? BasePairConnectorIsVisible 
        {
            get { return null; }
            set { throw new NotSupportedException(); }
        }

        public Brush BasePairConnectorFill 
        {
            get { return null; }
            set { throw new NotSupportedException(); }
        }

        public Brush BasePairConnectorStroke 
        {
            get { return null; }
            set { throw new NotSupportedException(); }
        }

        public double? BasePairConnectorStrokeThickness 
        {
            get { return null; }
            set { throw new NotSupportedException(); }
        }

        public double? BasePairConnectorSize 
        {
            get { return null; }
            set { throw new NotSupportedException(); }
        }

        public BasePairConnectorRenderingMode? BasePairConnectorRenderingStyle 
        {
            get { return null; }
            set { throw new NotSupportedException(); }
        }

        public bool IsBasePairConnectorFillInheriting()
        {
            throw new NotSupportedException();
        }

        public bool IsBasePairConnectorStrokeInheriting()
        {
            throw new NotSupportedException();
        }

        public bool IsNucleotideFillInheriting()
        {
            var fill = GetValue(NucleotideFillProperty);
            return (fill == null) ? true : false;
        }

        public bool IsNucleotideCircleFillInheriting()
        {
            var fill = GetValue(NucleotideCircleFillProperty);
            return (fill == null) ? true : false;
        }

        public bool IsNucleotideCircleStrokeInheriting()
        {
            var stroke = GetValue(NucleotideCircleStrokeProperty);
            return (stroke == null) ? true : false;
        }

        public void StartEditTracking()
        {
            if (PropertyContextManager == null)
            {
                PropertyContextManager = new NucleotideVisualComponentPropertyContext(this);
            }
        }

        public void ApplyPropertyEditSet(StructureElementPropertyChangeSet changeSet, bool interruptEditTracking)
        {
            if (PropertyContextManager != null)
            {
                if (PropertyContextManager.BatchEdit(changeSet))
                {
                    PauseEditTracking = true;
                    PropertyContextManager.Apply(); //Synchronize
                    PauseEditTracking = false;
                    if(!interruptEditTracking) OnNucleotideEdited();
                }
            }
        }

        public void InterruptEditTracking()
        {
            PauseEditTracking = true;
        }

        public void UninterruptEditTracking()
        {
            PauseEditTracking = false;
        }

        public void InterruptEditNotification()
        {
            PauseEditNotification = true;
        }

        public void UninterruptEditNotification()
        {
            PauseEditNotification = false;
        }

        public void UndoAllPropertyChanges()
        {
            if (PropertyContextManager != null)
            {
                PropertyContextManager.UndoAll();
                InterruptEditTracking();
                PropertyContextManager.Apply(); //We tell the property context manager to apply the rolled back state to us
                UninterruptEditTracking();
            }
        }

        public void UndoLastPropertyChange()
        {
            if (PropertyContextManager != null)
            {
                PropertyContextManager.UndoLast();
                InterruptEditTracking();
                PropertyContextManager.Apply(); //We tell the property context manager to apply the rolled back state to us
                UninterruptEditTracking();
            }
        }

        public object SynchronizeProperties()
        {
            if (PropertyContextManager != null)
            {
                return PropertyContextManager.Save(); //We don't dump the edited state
            }
            return null;
        }

        public object SaveProperties()
        {
            if (PropertyContextManager != null)
            {
                object retValue = PropertyContextManager.Save();
                PropertyContextManager = new NucleotideVisualComponentPropertyContext(this);
                return retValue;
            }
            return null;
        }

        public bool IsDirty()
        {
            if (PropertyContextManager == null)
                return false;
            return (PropertyContextManager.EditCount() > 0) ? true : false;
        }

        #endregion

        public void UpdateConnectedNucleoidePositionMoveBy(Point delta)
        {
            NucleotideConnector.MoveNextNucleotideBy(delta);
        }

        public void UpdateConnectedNucleotidePositionMoveTo(Point location)
        {
            NucleotideConnector.MoveNextNucleotideTo(location);
        }

        public void UpdatePreviousNucleotidePositionMoveBy(Point delta)
        {
            NucleotideConnector.MovePreviousNucleotideBy(delta);
        }

        public void UpdatePreviousNucleotidePositionMoveTo(Point location)
        {
            NucleotideConnector.MovePreviousNucleotideTo(location);
        }

        /*public Rect PreviewMoveTo(Point location)
        {
            Point delta = new Point(location.X - Bounds.TopLeft.X, location.Y - Bounds.TopLeft.Y);
            return PreviewMoveBy(delta);
        }*/

        /*public Rect PreviewMoveBy(Point delta)
        {
            return Rect.Offset(Bounds, delta.X, delta.Y);
        }*/

        /*public void MoveTo(Point location)
        {
            Point delta = new Point(location.X - Bounds.TopLeft.X, location.Y - Bounds.TopLeft.Y);
            MoveBy(delta);
        }*/

        public void MoveBy(Point delta)
        {
            Bounds = Rect.Offset(Bounds, delta.X, delta.Y);
            Location = new Point(Bounds.TopLeft.X + Math.Round(Bounds.Width / 2.0), Bounds.TopLeft.Y + Math.Round(Bounds.Height / 2.0));
            NucleotideConnector.MoveBy(delta);
            PhysicalNtVisual.Render();
        }

        static NucleotideStructureElementVisualComponent()
        {
            INTERNAL_DEFAULT_NUCLEOTIDE_FILL.Freeze();
            INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_FILL.Freeze();
            INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_STROKE.Freeze();
            TRANSPARENT_BRUSH.Freeze();
        }

        public NucleotideStructureElementVisualComponent(NucleotideVector vectorToNextNucleotide, IBioSymbol data, int nucleotideIndex)
            : this(vectorToNextNucleotide, data, nucleotideIndex, new Point(0, 0)) { }

        public NucleotideStructureElementVisualComponent(NucleotideVector vectorToNextNucleotide, IBioSymbol data, int nucleotideIndex, Point location)
        {
            Debug.Assert(data != null);
            Debug.Assert(vectorToNextNucleotide != null);
            PhysicalNtVisual = new NucleotideDrawingVisual(this);
            NucleotideConnector = vectorToNextNucleotide;
            NucleotideIndex = nucleotideIndex;
            Data = data;
            Location = location;
            PauseEditTracking = false;
            PauseEditNotification = false;
            _isComponentVisible = true; //Let it be set to false.
            Bounds = PhysicalNtVisual.Measure();
            ContextMessage = BuildContextMessage();
        }

        private NucleotideVisualComponentPropertyContext PropertyContextManager { get; set; }
        private NucleotideDrawingVisual PhysicalNtVisual { get; set; }
        private bool PauseEditTracking { get; set; }
        private bool PauseEditNotification { get; set; }
        private bool _isComponentVisible = true;
        private bool _isSelected = false;
        private bool _isContextuallySelected = false;
        private bool _showNucleotideConnectors = false;

        private bool _isSelectable = true;
        private bool IsSelectable
        {
            get { return _isSelectable; }
            set
            {
                bool newSelectableState = value;
                if (!newSelectableState && IsSelected)
                    IsSelected = false; //Clear the selection flag if selected
                _isSelectable = newSelectableState;
            }
        }

        private string BuildContextMessage()
        {
            BasePairStructureElementVisualComponent parentBasePair = null;
            HelixStructureElementVisualComponent parentHelix = null;
            LoopStructureElementVisualComponent parentLoop = null;
            if (ParentComponent != null)
            {
                parentBasePair = ParentComponent as BasePairStructureElementVisualComponent;
                if (parentBasePair != null)
                {
                    parentHelix = parentBasePair.ParentComponent as HelixStructureElementVisualComponent;
                    if (parentHelix != null)
                    {
                        return (parentBasePair.Data.FivePrimeIndex == NucleotideIndex) ? string.Format(@"Nucleotide {0}, 5' Nucleotide, paired to {1}. Member of helix with 5' Strand from {2}-{3} and 3' Strand from {4}-{5}", NucleotideIndex + 1, parentBasePair.Data.ThreePrimeIndex + 1, parentHelix.FivePrimeStartNucleotideIndex + 1, parentHelix.FivePrimeEndNucleotideIndex + 1, parentHelix.ThreePrimeStartNucleotideIndex + 1, parentHelix.ThreePrimeEndNucleotideIndex + 1) :
                            string.Format(@"Nucleotide {0}, 3' Nucleotide, paired to {1}. Member of helix with 5' Strand from {2}-{3} and 3' Strand from {4}-{5}", NucleotideIndex + 1, parentBasePair.Data.FivePrimeIndex + 1, parentHelix.FivePrimeStartNucleotideIndex + 1, parentHelix.FivePrimeEndNucleotideIndex + 1, parentHelix.ThreePrimeStartNucleotideIndex + 1, parentHelix.ThreePrimeEndNucleotideIndex + 1);
                    }
                    else
                    {
                        return (parentBasePair.Data.FivePrimeIndex == NucleotideIndex) ? string.Format(@"Nucleotide {0}, 5' Nucleotide, paired to {1}", NucleotideIndex + 1, parentBasePair.Data.ThreePrimeIndex + 1) :
                            string.Format("Nucleotide {0}, 3' Nucleotide, paired to {1}.", NucleotideIndex + 1, parentBasePair.Data.FivePrimeIndex + 1);
                    }
                }
                else 
                {
                    parentLoop = ParentComponent as LoopStructureElementVisualComponent;
                    if (parentLoop != null)
                    {
                        if (parentLoop.StartNucleotideIndex == NucleotideIndex)
                        {
                            return string.Format(@"Nucleotide {0}, First Nucleotide of {1} Loop Strand from {2}-{3}", NucleotideIndex + 1, parentLoop.StrandType, parentLoop.StartNucleotideIndex + 1, parentLoop.EndNucleotideIndex + 1);
                        }
                        else if (parentLoop.EndNucleotideIndex == NucleotideIndex)
                        {
                            return string.Format(@"Nucleotide {0}, Last Nucleotide of {1} Loop Strand from {2}-{3}", NucleotideIndex + 1, parentLoop.StrandType, parentLoop.StartNucleotideIndex + 1, parentLoop.EndNucleotideIndex + 1);
                        }
                        else
                        {
                            return string.Format(@"Nucleotide {0}, Member of {1} Loop Strand from {2}-{3}", NucleotideIndex + 1, parentLoop.StrandType, parentLoop.StartNucleotideIndex + 1, parentLoop.EndNucleotideIndex + 1);
                        }
                    }
                    else
                    {
                        return "Unknown.";
                    }
                }
            }
            else
            {
                return "Unknown.";
            }
        }

        private static readonly FontFamily INTERNAL_DEFAULT_NUCLEOTIDE_FONTFAMILY = new FontFamily("Arial");
        private static readonly FontWeight INTERNAL_DEFAULT_NUCLEOTIDE_FONTWEIGHT = FontWeights.Normal;
        private static readonly FontStyle INTERNAL_DEFFAULT_NUCLEOTIDE_FONTSTYLE = FontStyles.Normal;
        private static readonly double INTERNAL_DEFAULT_NUCLEOTIDE_FONTSIZE = 8;
        private static readonly NucleotideRenderingMode INTERNAL_DEFAULT_NUCLEOTIDE_RENDERINGMODE = NucleotideRenderingMode.Text;
        private static readonly char INTERNAL_DEFAULT_NUCLEOTIDE_SUBSTITUTION = ' ';
        private static readonly bool INTERNAL_DEFAULT_NUCLEOTIDE_ISSUBSTITUTED = false;
        private static readonly Brush INTERNAL_DEFAULT_NUCLEOTIDE_FILL = new SolidColorBrush(Colors.DarkGoldenrod);
        private static readonly double INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_DIAMETER = 0.0; //The renderer will scale it to fill the space.
        private static readonly double INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_EDGETHICKENESS = 0.0;
        private static readonly bool INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_ISFILLED = false;
        private static readonly Brush INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_FILL = new SolidColorBrush(Colors.DarkGoldenrod);
        private static readonly Brush INTERNAL_DEFAULT_NUCLEOTIDE_CIRCLE_STROKE = new SolidColorBrush(Colors.DarkGoldenrod);
        private static readonly Brush TRANSPARENT_BRUSH = new SolidColorBrush(Colors.Transparent);
    }
}
